package com.novel.authorization.vip;

import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
import com.baomidou.mybatisplus.core.toolkit.ClassUtils;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.plugins.inner.BaseMultiTableInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import com.baomidou.mybatisplus.extension.toolkit.PropertyMapper;
import lombok.Getter;
import lombok.Setter;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.Parenthesis;
import net.sf.jsqlparser.expression.RowConstructor;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.MinorThanEquals;
import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.*;
import net.sf.jsqlparser.statement.update.Update;
import net.sf.jsqlparser.statement.update.UpdateSet;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.Properties;

/**
 * VipSqlParser
 *
 * @author 樊温军
 * @date 2025/3/25 下午2:49
 */
@Setter
@Getter
public class VipInnerInterceptor extends BaseMultiTableInnerInterceptor implements InnerInterceptor {
    private CustomVipHandler customVipHandler;

    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,
                            ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        if (!InterceptorIgnoreHelper.willIgnoreTenantLine(ms.getId())) {
            PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
            mpBs.sql(this.parserSingle(mpBs.sql(), null));
        }
    }

    @Override
    public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
        PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh);
        MappedStatement ms = mpSh.mappedStatement();
        SqlCommandType sct = ms.getSqlCommandType();
        if (sct == SqlCommandType.INSERT || sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) {
            if (InterceptorIgnoreHelper.willIgnoreTenantLine(ms.getId())) {
                return;
            }

            PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
            mpBs.sql(this.parserMulti(mpBs.sql(), null));
        }

    }

    @Override
    protected void processSelect(Select select, int index, String sql, Object obj) {
        String whereSegment = (String) obj;
        processSelectBody(select, whereSegment);
        List<WithItem> withItemsList = select.getWithItemsList();
        if (!CollectionUtils.isEmpty(withItemsList)) {
            withItemsList.forEach((withItem) -> {
                processSelectBody(withItem, whereSegment);
            });
        }

    }

    @Override
    protected void processInsert(Insert insert, int index, String sql, Object obj) {
        if (!this.customVipHandler.ignoreTable(insert.getTable().getName())) {
            List<Column> columns = insert.getColumns();
            if (!CollectionUtils.isEmpty(columns)) {
                String tenantIdColumn = this.customVipHandler.getVipColumn();
                if (!this.customVipHandler.ignoreInsert(columns, tenantIdColumn)) {
                    columns.add(new Column(tenantIdColumn));
                    Expression vip = this.customVipHandler.getVip();
                    List<UpdateSet> duplicateUpdateColumns = insert.getDuplicateUpdateSets();
                    if (CollectionUtils.isNotEmpty(duplicateUpdateColumns)) {
                        EqualsTo equalsTo = new EqualsTo();
                        equalsTo.setLeftExpression(new StringValue(tenantIdColumn));
                        equalsTo.setRightExpression(vip);
                        duplicateUpdateColumns.add(new UpdateSet(new Column(tenantIdColumn), vip));
                    }

                    Select select = insert.getSelect();
                    if (select instanceof PlainSelect) {
                        processInsertSelect(select, (String) obj);
                    } else {
                        if (insert.getValues() == null) {
                            throw ExceptionUtils.mpe("Failed to process multiple-table update, please exclude the " +
                                    "tableName or statementId", new Object[0]);
                        }

                        Values values = insert.getValues();
                        ExpressionList<Expression> expressions = (ExpressionList<Expression>) values.getExpressions();
                        if (expressions instanceof ParenthesedExpressionList) {
                            expressions.addExpression(vip);
                        } else if (CollectionUtils.isNotEmpty(expressions)) {
                            int len = expressions.size();

                            for (int i = 0; i < len; ++i) {
                                Expression expression = expressions.get(i);
                                if (expression instanceof Parenthesis) {
                                    ExpressionList rowConstructor =
                                            (new RowConstructor()).withExpressions(new ExpressionList(((Parenthesis) expression).getExpression(), vip));
                                    expressions.set(i, rowConstructor);
                                } else if (expression instanceof ParenthesedExpressionList) {
                                    ((ParenthesedExpressionList) expression).addExpression(vip);
                                } else {
                                    expressions.add(vip);
                                }
                            }
                        } else {
                            expressions.add(vip);
                        }
                    }

                }
            }
        }
    }

    @Override
    protected void processUpdate(Update update, int index, String sql, Object obj) {
        Table table = update.getTable();
        if (!this.customVipHandler.ignoreTable(table.getName())) {
            List<UpdateSet> sets = update.getUpdateSets();
            if (!CollectionUtils.isEmpty(sets)) {
                sets.forEach((us) -> {
                    us.getValues().forEach((ex) -> {
                        if (ex instanceof Select) {
                            processSelectBody((Select) ex, (String) obj);
                        }

                    });
                });
            }

            update.setWhere(andExpression(table, update.getWhere(), (String) obj));
        }
    }

    @Override
    protected void processDelete(Delete delete, int index, String sql, Object obj) {
        if (!this.customVipHandler.ignoreTable(delete.getTable().getName())) {
            delete.setWhere(this.andExpression(delete.getTable(), delete.getWhere(), (String) obj));
        }
    }

    protected void processInsertSelect(Select selectBody, final String whereSegment) {
        if (selectBody instanceof PlainSelect) {
            PlainSelect plainSelect = (PlainSelect) selectBody;
            FromItem fromItem = plainSelect.getFromItem();
            if (fromItem instanceof Table) {
                processPlainSelect(plainSelect, whereSegment);
                appendSelectItem(plainSelect.getSelectItems());
            } else if (fromItem instanceof Select) {
                Select subSelect = (Select) fromItem;
                appendSelectItem(plainSelect.getSelectItems());
                processInsertSelect(subSelect, whereSegment);
            }
        } else if (selectBody instanceof ParenthesedSelect) {
            ParenthesedSelect parenthesedSelect = (ParenthesedSelect) selectBody;
            processInsertSelect(parenthesedSelect.getSelect(), whereSegment);
        }

    }

    protected void appendSelectItem(List<SelectItem<?>> selectItems) {
        if (!CollectionUtils.isEmpty(selectItems)) {
            if (selectItems.size() == 1) {
                SelectItem<?> item = selectItems.get(0);
                Expression expression = item.getExpression();
                if (expression instanceof AllColumns) {
                    return;
                }
            }

            selectItems.add(new SelectItem<>(new Column(this.customVipHandler.getVipColumn())));
        }
    }

    protected Column getAliasColumn(Table table) {
        StringBuilder column = new StringBuilder();
        if (table.getAlias() != null) {
            column.append(table.getAlias().getName()).append(".");
        }

        column.append(this.customVipHandler.getVipColumn());
        return new Column(column.toString());
    }

    @Override
    public void setProperties(Properties properties) {
        PropertyMapper.newInstance(properties).whenNotBlank("customVipHandler", ClassUtils::newInstance,
                this::setCustomVipHandler);
    }

    @Override
    public Expression buildTableExpression(final Table table, final Expression where, final String whereSegment) {
        MinorThanEquals minorThanEquals = new MinorThanEquals();
        minorThanEquals.setLeftExpression(this.getAliasColumn(table));
        minorThanEquals.setRightExpression(this.customVipHandler.getVip());
        return this.customVipHandler.ignoreTable(table.getName()) ? null : minorThanEquals;
    }

}
